/**
 * 
 */
package neptune.manager.builder;

import neptune.manager.model.topology.IPAddress;
import neptune.manager.model.topology.NetworkInterface;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.Topology;
import neptune.manager.storage.ExperimentRepositoryException;
import neptune.manager.storage.ExperimentsRepository;
import neptune.manager.storage.StorageReadException;
import neptune.manager.storage.TopologyDefinitionException;
import neptune.manager.storage.TopologyStorageFactory;
import neptune.manager.utils.DebugLog;
import neptune.manager.utils.IllegalValueException;
import neptune.manager.utils.UnsignedByte;
import neptune.manager.utils.Logger.LogContext;
import neptune.manager.utils.Logger.MessageType;

/**
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * 
 */
public class ControlIPAddressBuilder {

	private ExperimentsRepository expRepository;
	private TopologyStorageFactory topologyFactory;
	private IPAddressRange range;

	public ControlIPAddressBuilder(ExperimentsRepository expRepository,
			TopologyStorageFactory topologyFactory, IPAddressRange range) {
		super();
		this.expRepository = expRepository;
		this.topologyFactory = topologyFactory;
		this.range = range;
	}

	public IPAddress buildControlIPAddress(Topology actualTopology,
			LogContext log) throws ExperimentRepositoryException,
			StorageReadException, TopologyDefinitionException,
			ControlAddressException {
		return buildControlIPAddress(range, actualTopology, expRepository,
				topologyFactory, log);
	}

	public boolean addressExists(IPAddress address, Topology actualTopology)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {
		return addressExists(address, actualTopology, expRepository,
				topologyFactory);
	}

	/**
	 * Costruisce un indirizzo IP appartenente alla rete di controllo. Il metodo
	 * garantisce che tale IP non appertanga gia' a nessun nodo di un
	 * esperimento, ne' alla topologia passata come argomento. L'indirizzo viene
	 * definito selezionando il primo indirizzo libero nel range di indirizzi
	 * passato.
	 * 
	 * 
	 * @param range
	 * @param actualTopology
	 * @param expRepository
	 * @param topologyFactory
	 * @return
	 * @throws ExperimentRepositoryException
	 * @throws StorageReadException
	 * @throws TopologyDefinitionException
	 * @throws ControlAddressException
	 */
	public static IPAddress buildControlIPAddress(IPAddressRange range,
			Topology actualTopology, ExperimentsRepository expRepository,
			TopologyStorageFactory topologyFactory, LogContext log)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException, ControlAddressException {

		DebugLog.printDetailedLog("Starting control IP generation",
				ControlIPAddressBuilder.class.getSimpleName());
		log.addMessage("Starting control IP generation", MessageType.NORMAL);

		IPAddress[] rangesStart = range.getRangeLowers();

		for (IPAddress lower : rangesStart) {
			boolean found = true;
			DebugLog.printDetailedLog("Range lower:" + lower,
					ControlIPAddressBuilder.class.getSimpleName());
			IPAddress generated = lower;
			while (addressExists(generated, actualTopology, expRepository,
					topologyFactory)) {
				DebugLog.printDetailedLog("Exists address:" + generated,
						ControlIPAddressBuilder.class.getSimpleName());
				found = true;
				UnsignedByte[] bytes = generated.getUnsignedBytes();

				int result = bytes[3].increment();
				try {
					if (result == 0 || result == 255) {
						bytes[3].setValue(1);
						result = bytes[2].increment();
						if (result == 0) {
							bytes[2].increment();
							result = bytes[1].increment();
							if (result == 0) {
								bytes[1].increment();
								result = bytes[0].increment();
							}
						}
					}
				} catch (IllegalValueException e1) {
					e1.printStackTrace();
				}

				if (result == 0) {
					found = false;
					break; // range terminato con certezza
				}

				// generated.setAddress(bytes);
				try {
					generated = new IPAddress(4, IPAddress
							.toAddressString(bytes), generated.getSubnetMask());
				} catch (IllegalValueException e1) {
					throw new RuntimeException(e1);
				}

				DebugLog.printDetailedLog("New generated address:" + generated,
						ControlIPAddressBuilder.class.getSimpleName());
				log.addMessage("New generated address:" + generated,
						MessageType.NORMAL);

				// Se non e' nel range di indirizzi termina
				try {
					if (!range.isInRange(generated)) {
						found = false;
						break;
					}
				} catch (IllegalValueException e) {
					found = false;
					break;
				}
			}

			if (found) {
				DebugLog.printDetailedLog("Final generated Control Address:"
						+ generated, ControlIPAddressBuilder.class
						.getSimpleName());
				log.addMessage("Final generated Control Address:" + generated,
						MessageType.NORMAL);
				generated.setControlAddress(true);
				return generated;
			}
		}

		log.addMessage("Impossible to generate a new control address",
				MessageType.ERROR);
		throw new ControlAddressException(
				"Impossible to generate a new control address, the control address range is full");
	}

	/**
	 * Verifica l'esistenza di un indirizzo IP fra tutte le topologie definite
	 * dal sistema e all'interno della topologia passata come argomento.
	 * 
	 * 
	 * @param address
	 * @param actualTopology
	 * @param expRepository
	 * @param topologyFactory
	 * @return
	 * @throws ExperimentRepositoryException
	 * @throws StorageReadException
	 * @throws TopologyDefinitionException
	 */
	public static boolean addressExists(IPAddress address,
			Topology actualTopology, ExperimentsRepository expRepository,
			TopologyStorageFactory topologyFactory)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {

		// Controlla sulla topologia che sta venendo modificata
		for (Node node : actualTopology.getNodes()) {
			for (NetworkInterface netIf : node.getInterfaces()) {
				if (address.equals(netIf.getControlAddress()))
					return true;
			}
		}

		return addressExistsInRepository(address, expRepository,
				topologyFactory);
	}

	public static boolean addressExistsInRepository(IPAddress address,
			ExperimentsRepository expRepository,
			TopologyStorageFactory topologyFactory)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {

		// Controlla sulle topologie presenti nel repository
		for (String expId : expRepository.getExperiments()) {
			Topology topology = topologyFactory.getTopologyStorage(expId)
					.getTopology();

			for (Node node : topology.getNodes()) {
				for (NetworkInterface netIf : node.getInterfaces()) {
					if (address.equals(netIf.getControlAddress()))
						return true;
				}
			}
		}

		return false;
	}

	/**
	 * @return the range
	 */
	public IPAddressRange getRange() {
		return range;
	}

	/**
	 * @param range
	 *            the range to set
	 */
	public void setRange(IPAddressRange range) {
		this.range = range;
	}

	public boolean addressExistsInRepository(IPAddress controlAddress)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {
		return addressExistsInRepository(controlAddress, expRepository,
				topologyFactory);
	}

	public static boolean addressExistsInRepository(IPAddress address,
			String topologyId, String nodeId, String ifName,
			ExperimentsRepository expRepository,
			TopologyStorageFactory topologyFactory)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {

		// Controlla sulle topologie presenti nel repository
		for (String expId : expRepository.getExperiments()) {
			Topology topology = topologyFactory.getTopologyStorage(expId)
					.getTopology();

			for (Node node : topology.getNodes()) {
				for (NetworkInterface netIf : node.getInterfaces()) {
					if (topology.getName().equals(topologyId)
							&& node.getId().equals(nodeId)
							&& netIf.getInterfaceName().equals(ifName))
						break;

					if (address.equals(netIf.getControlAddress()))
						return true;
				}
			}
		}

		return false;
	}

	/**
	 * Controlla se esiste nel repository l'IPAddress passato come argomento. La
	 * ricerca e' esclusa per l'interfaccia del nodo della topologia, i cui
	 * identificativi sono passati come argomento.
	 * 
	 * @param controlAddress
	 * @param topologyId
	 * @param node
	 * @param ifName
	 * @return
	 * @throws TopologyDefinitionException
	 * @throws StorageReadException
	 * @throws ExperimentRepositoryException
	 */
	public boolean addressExistsInRepository(IPAddress address,
			String topologyId, String nodeId, String ifName)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {
		return addressExistsInRepository(address, topologyId, nodeId, ifName,
				expRepository, topologyFactory);
	}
}
